/**
 * 
 */
package no.hig.ekm.ircclient;

import java.net.*;
import java.util.ArrayList;
import java.io.*;

/**
 * Gets INI file from web and parses it 
 * i.e. saves and stores the network and server data as objects
 * @author Kristofers
 * 
 */
public class IniParser {

	private static final char COMMENT = ';'; // String ";#"
	static final char SECTION_BEGIN = '[';
	static final char SECTION_END = ']';
	static final String INIURL = "http://www.mirc.com/servers.ini"; // "http://10.0.0.2/servers.ini";
	static final String INIFILE = "servers.bin";
	static File file=new File(INIFILE);
	private String section = null;
	/* A list with Network objects */
	static ArrayList<Network> nets = new ArrayList<Network>(); 

	/**
	 * Enumerator with different kinds of sections
	 * with function for section comparison
	 */
	public enum Sections {
		TIMESTAMP, NETWORKS, SERVERS, OTHER;

		public static Sections toStr(String str) {
			try {
				return valueOf(str);
			} catch (Exception ex) {
				return OTHER;
			}
		}
	}

	/**
	 * Default constructor with some error handling
	 * Checks if the custom created file exists
	 * If so, read it, otherwise read the servers from net
	 */
	public IniParser() {
		try {
			if (file.exists())
				parse(file);
			else {
				parse(new URL(INIURL));
			}
			
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Reads custom created file
	 * and adds networks and servers from it
	 * @param file
	 */
	private void parse(File file) {
		System.out.print("Reading servers.bin file...");
		ObjectInputStream input;
		try {
			input = new ObjectInputStream(new FileInputStream(file.getAbsoluteFile()));
            while (true) {
                Network netData;
                netData = (Network) input.readObject();
                nets.add(new Network(netData.getName(),netData.getServers()));
            }
		} catch (EOFException eofe) {
            // End of file, breaking out of while loop. 
			System.out.println(" Ready!");
		} catch (IOException e) {
			e.printStackTrace();
        } catch (ClassNotFoundException cnfe) {
			System.err.println("Could not read Network object!");
        }

	}
	
	 /**
     * This function serializes Network and Server data and uses
     * ObjectOutputStream for writing the data to a file 
     * @param ostr
     */
    private static void serialize(ObjectOutputStream ostr) throws IOException {
        for (Network net : nets) {
        	Network tempNet = new Network();
        	tempNet.setName(net.getName());
        	tempNet.addServers(net.getServers());
            ostr.writeObject(tempNet);
            //System.out.println(tempNet);
        }
    }
    
    /**
     * Saves the current networks and servers to a custom file
     */
    public static void saveData() {
        ObjectOutputStream out;
		try {
			out = new ObjectOutputStream(new FileOutputStream(file));
			serialize(out);
            out.close(); 
            System.out.println("Data saved");
		} catch (IOException e) {
			e.printStackTrace();
		}
    }

	/**
	 * Gets an INI file from the given url
	 * reads it line by line and handles some errors
	 * @param url
	 */
	private void parse(URL url) {
		System.out.print("Getting servers.ini from net...");
		try {
			URLConnection yc = url.openConnection();
			BufferedReader in = new BufferedReader(new InputStreamReader(
					yc.getInputStream()));
			String inputLine;
			
			while ((inputLine = in.readLine()) != null) {
				lineRead(inputLine);
			}
			in.close();
			System.out.println(" Ready!");
		} catch (MalformedURLException mue) {
			mue.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Recursive function Reads string until it gets limiter char.
	 * 
	 * @param stringToRead
	 *            string to be read
	 * @return If there is not limiter, returns null, otherwise substring to
	 *         limiter
	 */
	private void lineRead(String stringToRead) {
		/* 
		 * If the line is not empty and not a comment, keep on..
		*/
		if (!stringToRead.isEmpty() && !is(stringToRead, COMMENT)) { 
			/* If the line is a section header, read it */
			if (is(stringToRead, SECTION_BEGIN)) {
				section = readSectionHeader(stringToRead);
				/* If a section has been found, read its contents */
			} else if (!section.isEmpty()) {
				/* Get the type of section and call corresponding function */
				switch (Sections.toStr(section.toUpperCase())) {
				case TIMESTAMP:
					System.out.println(stringToRead);
					break;
				case NETWORKS:
					parseNetworks(stringToRead);
					break;
				case SERVERS:
					parseServers(stringToRead);
					break;
				default:
					System.out.println("ERROR: Not a valid section name");
					break;
				}
				// System.out.println("He: " + stringToRead);
			}
		}

	}

	/**
	 * For every string in the Servers section
	 * get the server data and add them to the 
	 * corresponding network
	 * @param str
	 */
	private void parseServers(String str) {
		/* 
		 * Split the array in 4 parts:
		 * name, address, port range and network
		 */
		String[] parts = str.split(":");
		String url = parts[1];
		String name = parts[0].replaceAll("(n\\d+=?)(.+?)(SERVER$)", "$2").trim();
		String port = parts[2].replaceAll("(.+?)(GROUP$)", "$1");
		// Find the network to add belonging server
		String netName = parts[parts.length - 1].trim();
		Network net = find(netName);
		// If the network was not in the "Group" section in INI..
		if (net == null) {
			// Look for the "network" by server name
			net = find(name);
			// If did not find anything, create a new network
			// with the server name
			if (net == null) {
				net = new Network(name);
				//name = netName; //Save the group name as server name
				nets.add(net);
			}
		}
		//If we are adding to the Random server network, use server's group name 
			name = net.getName().equals("Random server") ?  netName : name;
		net.addServer(name, url, port);
	}

	/**
	 * Looks for the server by its name
	 * @param name
	 * @return Network
	 */
	public static Network find(String name) {
		for (Network net : nets) {
			if (!net.equals(null) && net.getName().equalsIgnoreCase(name)) {
				return net;
			}
		}
		return null;
	}
	
	/**
	 * Looks for the server by its name
	 * @param name
	 * @return Server
	 */
	public static Server find(String network, String serverName) {
		Network net = find(network);
		if (!net.equals(null)) {
			
		for (Server server : net.getServers()) {
			if (server.getName().equalsIgnoreCase(serverName)) {
				return server;
			}
		}
		}
		return null;
	}
	
	/**
	 * Checks whether the given network exists
	 * @param name
	 * @return - boolean
	 */
	public static boolean exists(String name) {
		if (!nets.isEmpty()) {
			Network n = find(name);
			if (!(n == null)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * For the Networks section
	 * adds new Networks w/o server data for now
	 * @param str
	 */
	private void parseNetworks(String str) {
		String[] parts = str.split("=");
		nets.add(new Network(parts[1]));
		//System.out.println("Network added");
	}


	/**
	 * Checks the string by the char parameter
	 * 
	 * @param string
	 * @param ch
	 * @return - boolean
	 */
	private boolean is(String string, char ch) {
		if (string.charAt(0) == ch) {
			return true;
		}
		return false;
	}

	/**
	 * Reads and returns the section's name
	 * @param sectionLine
	 * @return - String
	 */
	private String readSectionHeader(String sectionLine) {
		for (int i = 1; i < sectionLine.length(); i++) {
			if (sectionLine.charAt(i) == SECTION_END) {
				return sectionLine.substring(1, i);
			}
		}
		return null;
	}
	
	/**
	 * Checks whether the local file has been created
	 * @return
	 */
	public static boolean usingLocalFile() {
		if (file.exists()) 
			return true;
		else
			return false;
	}

}
